#include <reg52.h>
#include <INTRINS.H>
typedef unsigned char uint8_t;
typedef signed char int8_t;
typedef unsigned int uint16_t;
typedef signed int int16_t;
typedef unsigned long uint32_t;
typedef signed long int32_t;
typedef code unsigned char type_cbyte;
#define NULL ((void*) 0)
#define false 0
#define true 1

//////////////////////////////////////////////////////////////////////////////
// Common subroutines
// Event handlers
void on_init(void);
void (*on_keydown)(uint8_t key) = NULL;

uint16_t frame_count;
uint8_t key_state[LCD_BUTTON_ROWS * LCD_BUTTON_COLUMNS];
uint32_t rand_seed;

void delay_clock(uint8_t x) {
	while (x--);
}

// 1010010 -> 0100101
uint8_t reverse_byte(uint8_t x) {
	type_cbyte lookup[] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
	return ((lookup[x & 0x0f]) << 4) | lookup[(x & 0xf0) >> 4];
}

#define delay_dual _nop_();_nop_

// 60 FPS
void delay_frame(void) {
	register uint8_t i = 44, j;
	while (--i) for (j = 173; --j; );
}

//////////////////////////////////////////////////////////////////////////////
// LED
sbit led_inv = P1 ^ 0; // 0 = lit

//////////////////////////////////////////////////////////////////////////////
// LCD
#define lcd_db P2
sbit lcd_bf = lcd_db ^ 7;
sbit lcd_rs = P0 ^ 7;
sbit lcd_rw = P0 ^ 6;
sbit lcd_enable = P0 ^ 0;
sbit lcd_rst = P0 ^ 1; // 0 = reset

void lcd_reset(void) {
	lcd_rst = 0;
	delay_clock(250);
	delay_clock(250);
	delay_clock(250);
	delay_clock(250);
	lcd_rst = 1;
	delay_clock(250);
	delay_clock(250);
	delay_clock(250);
	delay_clock(250);
}

// wait LCD to complete
void lcd_join(void) {
	register uint16_t timeout;
	lcd_enable = 0;
	lcd_rs = 0;
	lcd_rw = 1;
	lcd_db = 0xff;
	lcd_enable = 1;
	// time out after about 65,300 loop cycles
	for (timeout = 233; lcd_bf && timeout; timeout++);
	lcd_enable = 0;
}

void lcd_command(uint8_t command) {
	lcd_join();
	lcd_enable = 0;
	lcd_rs = 0;
	lcd_rw = 0;
	delay_dual(); lcd_enable = 1;
	delay_dual(); lcd_db = command;
	delay_dual(); lcd_enable = 0;
}

void lcd_data(uint8_t db) {
	lcd_join();
	lcd_enable = 0;
	lcd_rs = 1;
	lcd_rw = 0;
	delay_dual(); lcd_enable = 1;
	delay_dual(); lcd_db = db;
	delay_dual(); lcd_enable = 0;
}

uint8_t lcd_read(void) {
	register uint8_t r;
	lcd_join();
	lcd_enable = 0;
	lcd_rs = 1;
	lcd_rw = 1;
	lcd_db = 0xff;
	delay_dual(); lcd_enable = 1;
	delay_dual(); r = lcd_db;
	delay_dual(); lcd_enable = 0;
	return r;
}

// LCD internal coordinates:
//     0x80..0x87 = Line 0
//     0x90..0x97 = Line 1
//     0x88..0x8f = Line 2
//     0x98..0x9f = Line 3
type_cbyte lcd_line_to_base_address[4] = {0x80, 0x90, 0x88, 0x98};

// Cache the status bits since they cannot be queried
// Constant bits set on initialization:
//     DL = 1
//     G = 1
//     I/D = 1
//     S = 0
// RE should be set everywhere with command 0x32 and 0x34.
bit lcd_visible;
bit lcd_cursor;
bit lcd_cursor_blink;
bit lcd_sr; // 0 = CGRAM address; 1 = scroll top
uint8_t lcd_scroll_top;
uint8_t lcd_cursor_line, lcd_cursor_column;
// 1 = negate all following graphical operations
// useful if you want to swap the two colors of the LCD
bit lcd_inv;
// The address has just been set,
// which means a dummy precedent is required for the next read
bit lcd_address_just_set;

void lcd_clear(void) {
	register uint8_t x, y, data;
	lcd_command(0x);
	data = extendbit(lcd_inv, uint8_t);
	lcd_command(0x36);
	for (y = 0; y < 32; y++) {
		lcd_command(0x80 | y);
		lcd_command(0x80);
		for (x = 0; x < 32; x++) lcd_data(data);
	}
}

//void lcd_draw_picture(uint8_t* p) {
//	uint8_t x, y;
//	for (y = 0; y < 32; y++) {
//		lcd_command(0x80 | y);
//		lcd_command(0x80);
//		for (x = 0; x < 16; x++) lcd_data(p[y << 4 + x]);
//		for (x = 0; x < 16; x++) lcd_data(p[512 + y << 4 + x]);
//	};
//}

// x: 0..7
// y: 0..63
void lcd_set_address(uint8_t x, uint8_t y) {
	lcd_command(0x36);
	lcd_command(0x80 | (y & 31));
	lcd_command(0x80 | ((y & 0x20) >> 2) | x);
	lcd_address_just_set = true;
}

uint16_t lcd_read_word(void) {
	register uint16_t r;
	if (lcd_address_just_set) {
		lcd_address_just_set = false;
		lcd_read();
	}
	r = reverse_byte(lcd_read());
	r |= ((uint16_t) reverse_byte(lcd_read())) << 8;
	if (lcd_inv) r = ~r;
	return r;
}

void lcd_write_word(uint16_t word) {
	lcd_address_just_set = false;
	if (lcd_inv) word = ~word;
	lcd_data(reverse_byte((uint8_t) word));
	lcd_data(reverse_byte(word >> 8));
}

void lcd_set_text(char xdata* text) {
	register uint8_t i;
	lcd_command(0x32);
	lcd_command(0x80);
	for (i = 0; i < 16; i++) lcd_data(text[i]);
	for (i = 32; i < 48; i++) lcd_data(text[i]);
	lcd_command(0x90);
	for (i = 16; i < 32; i++) lcd_data(text[i]);
	for (i = 48; i < 64; i++) lcd_data(text[i]);
	lcd_command(0xa0);
	for (i = 32; i < 48; i++) lcd_data(text[i]);
	for (i = 64; i < 80; i++) lcd_data(text[i]);
}

void lcd_set_visible(bit visible) {
	lcd_visible = visible;
	lcd_command(
		0x08
		| ((uint8_t) lcd_visible << 2)
		| ((uint8_t) lcd_cursor << 1)
		| ((uint8_t) lcd_cursor_blink)
	);
}

void lcd_set_cursor(uint8_t line, uint8_t column, bit blink) {
	lcd_command(0x32);
	lcd_command(lcd_line_to_base_address[line] | column);
	lcd_cursor = 1;
	lcd_cursor_blink = blink;
	lcd_cursor_line = line;
	lcd_cursor_column = column;
	lcd_command(
		0x0a
		| ((uint8_t) lcd_d << 2)
		| ((uint8_t) lcd_b)
	);
}

void lcd_hide_cursor(void) {
	lcd_command(0x32);
	lcd_cursor = 0;
	lcd_command(
		0x08
		| ((uint8_t) lcd_d << 2)
	);
}

void lcd_init(void) {
	lcd_reset();
	lcd_command(0x32);
	lcd_command(0x32); // RE = 0
	lcd_visible = 1;
	lcd_cursor = 1;
	lcd_cursor_blink = 1;
	lcd_command(0x0f); // D = 1; C = 1; B = 1
	lcd_command(0x06); // I/D = 1; S = 0
	lcd_cursor_line = 0;
	lcd_cursor_column = 0;
	lcd_command(0x02); // address reset
	lcd_command(0x36); // RE = 1
	lcd_command(0x36); // G = 1
	lcd_sr = 1;
	lcd_command(0x03); // SR = 1
	lcd_scroll_top = 0;
	lcd_command(0x40); // scroll top = 0
}

//////////////////////////////////////////////////////////////////////////////
// Keyboard

// You should print the text on your button physically
#define lcd_clear_button_text()
#define lcd_set_button_text(index, text)

// row - input default to 1
#define key_rp P3
sbit key_r0 = P3 ^ 2;
sbit key_r1 = P3 ^ 3;
sbit key_r2 = P3 ^ 4;
sbit key_r3 = P3 ^ 5;
sbit key_r4 = P3 ^ 6;
sbit key_r5 = P3 ^ 7;
#define for_key_r_unroll \
	for_key_r(0, key_r0) for_key_r(1, key_r1) for_key_r(2, key_r2) \
	for_key_r(3, key_r3) for_key_r(4, key_r4) for_key_r(5, key_r5)

// column - output 0 to check
#define key_cp P1
sbit key_c0 = P1 ^ 2;
sbit key_c1 = P1 ^ 3;
sbit key_c2 = P1 ^ 4;
sbit key_c3 = P1 ^ 5;
sbit key_c4 = P1 ^ 6;
#define for_key_c_unroll \
	for_key_c(0, key_c0) for_key_c(1, key_c1) for_key_c(2, key_c2) \
	for_key_c(3, key_c3) for_key_c(4, key_c4)

void key_init(void) {
	unsigned char i;
	#define for_key_r(i, p) p = 1;
	for_key_r_unroll
	#undef for_key_r
	for (i = 0; i < LCD_BUTTON_ROWS * LCD_BUTTON_COLUMNS; i++) {
		key_state[i] = 0;
	}
}

void key_pressing(uint8_t key) {
	switch (key_state[key]++) {
	case 30:
		key_state[key] = 26;
	case 0:
	case 25:
		if (on_keydown) on_keydown(key);
		break;
	}
}

void key_scan(void) {
	register unsigned char column, k;
	#define for_key_r(i, p) \
		k = column + i * LCD_BUTTON_COLUMNS; \
		if (p) { \
			key_state[k] = 0; \
		} else { \
			key_pressing(k); \
		}
	#define for_key_c(i, p) \
		column = i; \
		p = 0; delay_dual(); \
		for_key_r_unroll \
		p = 1; delay_dual();
	for_key_c_unroll
	#undef for_key_r
	#undef for_key_c
}

//////////////////////////////////////////////////////////////////////////////
// Main
int main(void) {
	_nop_();
	lcd_init();
	key_init();
	led_inv = 0;
	lcd_g_clear();
	frame_count = 0;
	on_init();
	for (;;) {
		delay_frame();
		key_scan();
		frame_count++;
	}
}
